package com.yuqih.common.dto;

import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import com.yuqih.common.annotation.FieldSpecifiedReference;
import com.yuqih.common.exception.CodedException;

public class FieldSpecifiedUtils {

	@SuppressWarnings("unchecked")
	public static <T> T createProxyInstance(Class<T> clazz) {
		Enhancer enhancer = new Enhancer();
		enhancer.setClassLoader(clazz.getClassLoader());

		enhancer.setSuperclass(clazz);
		enhancer.setInterfaces( new Class<?>[]{ IFieldSpecified.class} );
		enhancer.setCallback(new Interceptor(clazz));
		// enhancer.setUseCache(true); 这个是默认true的，这样对同一个类不会多次生成Class

		return (T) enhancer.create();
	}
	/**
	 * 清除condition中的字段为null或者为""的字段
	 * @param condition
	 */
	public static void cleanCondition(IFieldSpecified condition) {
		Iterator<String> iterator = condition.getSettedFieldNames().iterator();
		while (iterator.hasNext()) {
			String fieldName = iterator.next();
			Object prop;
			try {
				prop = PropertyUtils.getProperty(condition, fieldName);
			} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
				throw CodedException.from(e);
			}
			if (prop == null) {
				iterator.remove();
				continue;
			}
			if (prop instanceof String) {
				if (StringUtils.isBlank((CharSequence) prop)) {
					iterator.remove();
					continue;
				}
			}
		}
	}
	
	private static class Interceptor implements MethodInterceptor, Serializable {

		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		// 要代理的原始对象
		private final Class<?> clazz;
		private final Set<String> fieldNames = new HashSet<String>();
		private final Set<String> changedFieldNames = new HashSet<String>();
		private boolean monitorState = false;

		Interceptor(Class<?> clazz) {
			this.clazz = clazz;
		}

		@Override
		public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
			if (StringUtils.equals(method.getName(), "getSettedFieldNames") && method.getParameterTypes().length == 0) {
				return fieldNames;
			}
			
			if (StringUtils.equals(method.getName(), "getChangedFieldNames") && method.getParameterTypes().length == 0) {
				return changedFieldNames;
			}
			
			// setMonitorChangedFieldNames
			if (StringUtils.equals(method.getName(), "setMonitorChangedFieldNames") && method.getParameterTypes().length == 1
					&& Objects.equals(method.getParameterTypes()[0], Boolean.TYPE) ) {
				monitorState = (boolean) args[0];
				return null;
			}
			
			// hashCode方法
			if (StringUtils.equals(method.getName(), "hashCode") && method.getParameterTypes().length == 0) {
				return hashCode((int) proxy.invokeSuper(obj, args));
			}
			
			// equals方法
			if (StringUtils.equals(method.getName(), "equals") && method.getParameterTypes().length == 1
					&& Objects.equals(method.getParameterTypes()[0], Object.class)) {
				if (!isFieldNamesEqual(args[0])) {
					return false;
				}
				return proxy.invokeSuper(obj, args);
			}

			Object result = proxy.invokeSuper(obj, args);

			PropertyDescriptor[] pds = PropertyUtils.getPropertyDescriptors(clazz);

			for (PropertyDescriptor pd : pds) {

				if (Objects.equals(method, pd.getWriteMethod())) {
					
					FieldSpecifiedReference reference = method.getAnnotation(FieldSpecifiedReference.class);
					
					if (reference == null) {
						fieldNames.add(pd.getName());
						if (monitorState) {
							changedFieldNames.add(pd.getName());
						}
					} else {
						fieldNames.addAll( Arrays.asList(reference.value()));
						if (monitorState) {
							changedFieldNames.addAll(Arrays.asList(reference.value()));
						}
					}
					break;
				}
			}

			return result;
		}
		
		private int hashCode(int hashCode) {
			final int prime = 31;
			int result = 1;
			result = prime * result + fieldNames.hashCode();
			result = prime * result + hashCode;
			return result;
		}
		
		private boolean isFieldNamesEqual(Object o) {
			
			if (o == null) {
				return false;
			}
			
			if (o instanceof IFieldSpecified) {
				IFieldSpecified fieldSpecified = (IFieldSpecified)o;
				if (!Objects.equals(fieldNames, fieldSpecified.getSettedFieldNames())) {
					return false;
				} else {
					return true;
				}
			} else {
				return false;
			}
			
		}

	}

}
